package com.llbnk;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

/**
 * 滑动窗口限流法
 * @author shkstart
 * @create 2022-10-16 19:30
 */
public class SlidingWindowRateLimiter implements RateLimiter {
    // 每分钟限制的请求数
    private final long permitsPerMinute;

    // 计数器，k为当前窗口的开始时间值秒，value为当前窗口的计数
    private final TreeMap<Long, Integer> counters;

    public SlidingWindowRateLimiter(long permitsPerMinute) {
        this.permitsPerMinute = permitsPerMinute;
        this.counters = new TreeMap<>();
    }

    @Override
    public synchronized boolean tryAcquire() {
        // 获取当前时间所在的子窗口值；10s一个窗口
        //LocalDateTime.now()指的是2022-10-16 T19:47:28.131形式的时间
        //LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)为转换成秒级别的时间戳
        //LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) / 10 * 10其实就是将10s中的时间创造唯一一个key
        //举例子:LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)      1665949648
        //LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) / 10        166594964
        //LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) / 10 * 10  1665949640
        //也就是说1665949640-1665949649这10秒钟 大家的currentWindowTime都是1665949640，也就做到了10s一个窗口
        long currentWindowTime = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) / 10 * 10;
        // 获取当前窗口的请求总量
        int currentWindowCount = getCurrentWindowCount(currentWindowTime);
        if (currentWindowCount >= permitsPerMinute) {
            return false;
        }
        // 计数器加一
        //这merge的意思就是当前这个currentWindowTime是否存在？如果存在就+1，如果不存在就设置为1
        counters.merge(currentWindowTime, 1, Integer::sum);
        return true;
    }

    /**
     * 获取当前窗口的所有请求数（并删除所有无效的子窗口计数器）
     *
     * @param currentWindowTime 当前子窗口时间
     * @return 当前窗口中的计数
     */
    private int getCurrentWindowCount(long currentWindowTime) {
        // 计算出窗口的开始位置时间
        //这个设定是1min之内有60s之内有6个窗口 10s一个窗口 也就是说每一次计算都因随着时间的继续
        //前面滑动窗口被淘汰
        long startTime = currentWindowTime - 50;
        int result = 0;
        // 遍历当前存储的计数器，删除无效的子窗口计数器，并累加当前窗口中的所有计数器之后
        Iterator<Map.Entry<Long, Integer>> entryIterator = counters.entrySet().iterator();
        while (entryIterator.hasNext()) {
            Map.Entry<Long, Integer> entry = entryIterator.next();
            if (entry.getKey() < startTime) {
                entryIterator.remove();
            } else {
                result += entry.getValue();
            }
        }
        return result;
    }
}
